package com.mo.tools.log.oplog;


import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.ObjectUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.ClassUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@RequiredArgsConstructor
@Aspect
@Component
@Slf4j
public class LogRecordAspect {
//    @Resource
//    private IPolicyLogService policyLogService;
    @Resource
    private PlatformTransactionManager transactionManager;

    private final ExpressionParser parser = new SpelExpressionParser();
    private final String SP_EL_FLAG = "#";

    @Pointcut("@annotation(com.mo.tools.log.oplog.LogRecord)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        LogRecord logRecord = method.getAnnotation(LogRecord.class);
        boolean sameTransaction = logRecord.sameTransaction() == TrueOrFalse.TRUE;
        if (!sameTransaction) {
            Object proceed = joinPoint.proceed();
            try {
                recordLog(joinPoint, logRecord);
            } catch (Exception e) {
                log.error("记录操作日志异常: ", e);
            } finally {
                LogContext.clearContext();
            }
            return proceed;
        } else {
            // 事务处理
            DefaultTransactionDefinition def = new DefaultTransactionDefinition();
            def.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
            TransactionStatus status = transactionManager.getTransaction(def);
            try {
                // 执行目标方法
                Object result = joinPoint.proceed();
                try {
                    recordLog(joinPoint, logRecord);
                } catch (Exception e) {
                    log.error("记录操作日志异常: ", e);
                    throw new RuntimeException("记录操作日志异常");
                }
                if (!status.isCompleted()) {
                    transactionManager.commit(status);
                }
                return result;
            } catch (Throwable ex) {
                if (!status.isCompleted()) {
                    transactionManager.rollback(status);
                }
                throw ex;
            } finally {
                LogContext.clearContext();
            }
        }

    }

    /**
     * 保存记录
     * @param joinPoint
     * @param logRecord
     */
    private void recordLog(ProceedingJoinPoint joinPoint, LogRecord logRecord) {

        // 获取方法入参，key为参数名，value为参数值
        LinkedHashMap<String, Object> params = resolveParams(joinPoint);
        // 求值上下文
        StandardEvaluationContext context = new StandardEvaluationContext();
        if (params.size() == 1) {
            // 当参数只有一个时，设置根对象，例如入参为对象，则此时可以使用 #root.id 来获取对象的id
            params.forEach((k, v) -> context.setRootObject(v));
        }
        int i = 0;
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            context.setVariable(entry.getKey(), entry.getValue());
            // 设置参数别名，按顺序，可使用 #p0 来获取第一个入参
            context.setVariable("p" + i, entry.getValue());
            i++;
        }

        // 放入额外的参数和结果
        Object paramObj = LogContext.param.get();
        Object resultObj = LogContext.result.get();
        Object extMap = LogContext.extMap.get();
        if (ObjectUtils.isNotEmpty(paramObj)) {
            context.setVariable("param", paramObj);
        }
        if (ObjectUtils.isNotEmpty(resultObj)) {
            context.setVariable("result", resultObj);
        }
        if (ObjectUtils.isNotEmpty(extMap)) {
            context.setVariable("extMap", extMap);
        }

        String condition = resolveValue(logRecord.condition(), context);
        // 如果不满足记录日志条件，则返回
        if (!Boolean.parseBoolean(condition)) {
            return;
        }
        // 构建日志数据
        List<PolicyLog> policyLogList = buildLog(logRecord, context, params, joinPoint);
//        policyLogService.saveBatch(policyLogList);

    }

    /**
     * 构建日志数据
     *
     * @param logRecord
     * @param context
     * @param params
     * @param joinPoint
     * @return
     */
    private List<PolicyLog> buildLog(LogRecord logRecord, StandardEvaluationContext context, LinkedHashMap<String, Object> params, ProceedingJoinPoint joinPoint) {
        List<PolicyLog> policyLogList = Lists.newArrayList();
        List<String> bizNoList = Lists.newArrayList();
        OperationType operationType = logRecord.operationType();
        BizType bizType = logRecord.bizType();
        String bizNo = logRecord.bizNo();
        // 业务编号处理，是EL表示是的时候处理
        if (bizNo.contains(SP_EL_FLAG)) {
            Expression expression = parser.parseExpression(bizNo);
            Object bizObj = expression.getValue(context, Object.class);
            // 处理数组、字符串、列表，多个就生成多条记录
            if (ObjectUtils.isNotEmpty(bizObj)) {
                if (bizObj instanceof Collection) {
                    bizNoList = (List<String>) ((Collection) bizObj).stream().map(String::valueOf).collect(Collectors.toList());
                } else if (bizObj instanceof String || ClassUtils.isPrimitiveWrapper(bizObj.getClass())) {
                    bizNo = (String) bizObj;
                    String[] splitBizNos = bizNo.split("\\s*,\\s*");
                    bizNoList.addAll(Arrays.asList(splitBizNos));
                } else if (bizObj.getClass().isArray()) {
                    if (bizObj instanceof Long[]) {
                        bizNoList = Arrays.asList((Long[]) bizObj).stream().map(String::valueOf).collect(Collectors.toList());
                    } else if (bizObj instanceof String[]) {
                        bizNoList = Arrays.asList((String[]) bizObj);
                    }
                }
            }
        // 普通字符串处理
        } else if (ObjectUtils.isNotEmpty(bizNo)) {
            String[] splitBizNos = bizNo.split("\\s*,\\s*");
            bizNoList.addAll(Arrays.asList(splitBizNos));
        }
        List<String> finalBizNoList = bizNoList;
        boolean saveParams = logRecord.saveParams() == TrueOrFalse.TRUE;
        String className = joinPoint.getTarget().getClass().getSimpleName();
        IntStream.range(0, bizNoList.isEmpty() ? 1 : bizNoList.size()).forEach(index -> {
            String bizNoStr = finalBizNoList.isEmpty() ? "" : finalBizNoList.get(index);
            PolicyLog policyLog = new PolicyLog();
            policyLog.setBizNo(bizNoStr);
            // 默认业务类型和操作类型，只处理content
            if (operationType == OperationType.DEFAULT && bizType == BizType.DEFAULT) {
                String content = resolveValue(logRecord.content(), context);
                policyLog.setContent(content);
                policyLog.setContentEl(logRecord.content());
            // 使用业务类型和操作类型作为content
            } else {
                String bizDesc = bizType == null ? "" : bizType.getDesc();
                String operationTypeDesc = operationType == null ? "" : operationType.getDesc();
                policyLog.setContent(bizDesc + operationTypeDesc);
            }
            policyLog.setLogType(logRecord.logType().getCode());
            policyLog.setOperateType(operationType == null ? "" : operationType.getCode());
            policyLog.setBizType(bizType == null ? "" : bizType.getCode());
            policyLog.setLogCategory(resolveValue(logRecord.logCategory(), context));
            if (ObjectUtils.isEmpty(logRecord.logPage())) {
                policyLog.setLogPage(className);
            } else {
                policyLog.setLogPage(resolveValue(logRecord.logPage(), context));
            }
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            policyLog.setUrl(request.getRequestURI());
            policyLog.setRemark(resolveValue(logRecord.remark(), context));
            // 保存入参
            if (saveParams) {
                policyLog.setParams(JSONUtil.toJsonStr(params));
            }
            policyLogList.add(policyLog);
        });
        return policyLogList;
    }

    /**
     * 处理参数
     * @param joinPoint
     * @return
     */
    private LinkedHashMap<String, Object> resolveParams(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Object[] arguments = joinPoint.getArgs();
        String[] paramNames = getParameterNames(method);

        LinkedHashMap<String, Object> params = new LinkedHashMap<>();
        for (int i = 0; i < arguments.length; i++) {
            params.put(paramNames[i], arguments[i]);
        }
        return params;
    }

    /**
     * 处理入参
     * @param method
     * @return
     */
    private String[] getParameterNames(Method method) {
        ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
        return parameterNameDiscoverer.getParameterNames(method);
    }

    /**
     * 处理注解值
     * @param exp
     * @param context
     * @return
     */
    private String resolveValue(String exp, EvaluationContext context) {
        String value;
        // 如果包含#字符，则使用SpringEL表达式进行解析
        if (exp.contains(SP_EL_FLAG)) {
            value = resolveValueByExpression(exp, context);
        } else {
            // 否则不处理
            value = exp;
        }
        return value;
    }

    /**
     * 处理EL表达式
     * @param spELString
     * @param context
     * @return
     */
    private String resolveValueByExpression(String spELString, EvaluationContext context) {
        // 构建表达式
        try {
            Expression expression = parser.parseExpression(spELString);
            // 解析
            return expression.getValue(context, String.class);
        } catch (Exception e) {
            log.error("日志EL表达式解析异常,返回空值 [解析表达式:{}, 解析上下文:{}]", spELString, context, e);
            return "";
        }
    }

}
